package com.ctshk.rpc.payment.strategy;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.ctshk.common.dto.Result;
import com.ctshk.common.enums.SystemBusinessType;
import com.ctshk.common.enums.SystemError;
import com.ctshk.common.exception.BusinessException;
import com.ctshk.common.req.pay.NotifyConsumeReq;
import com.ctshk.common.utils.Assert;
import com.ctshk.rpc.order.air.service.IAirOrderService;
import com.ctshk.rpc.order.airexpress.service.IAirportExpressOrderAppService;
import com.ctshk.rpc.order.goods.service.IOrderService;
import com.ctshk.rpc.order.hotel.service.IHotelOrderH5Service;
import com.ctshk.rpc.order.localfun.service.ILocalFunAppOrderService;
import com.ctshk.rpc.order.scenic.service.IScenicOrderService;
import com.ctshk.rpc.order.scenic.service.app.IAirshowAppOrderService;
import com.ctshk.rpc.order.season.service.ISeasonOrderService;
import com.ctshk.rpc.order.tour.service.ISeriesOrderService;
import com.ctshk.rpc.order.train.service.ITrainOrderAppService;
import com.ctshk.rpc.payment.config.ApplicationContextProvider;
import com.ctshk.rpc.payment.dto.PayDetailsDTO;
import com.ctshk.rpc.payment.dto.RefundDetailsDTO;
import com.ctshk.rpc.payment.dto.req.PayRefundReq;
import com.ctshk.rpc.payment.entity.PayRecord;
import com.ctshk.rpc.payment.entity.RefundRecord;
import com.ctshk.rpc.payment.enums.PayOrderStatus;
import com.ctshk.rpc.payment.enums.PaymentMethod;
import com.ctshk.rpc.payment.enums.RefundStatus;
import com.ctshk.rpc.payment.mapper.PayRecordMapper;
import com.ctshk.rpc.payment.mapper.RefundRecordMapper;
import com.ctshk.rpc.payment.req.PayCommonReq;
import com.ctshk.rpc.payment.service.IPayConfigService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;

import java.time.LocalDateTime;
import java.util.Map;

/**
 * @BelongsProject: ctshk-cloud
 * @BelongsPackage: com.ctshk.rpc.payment.strategy
 * @Author: hef
 * @CreateTime: 2021-04-20 10:12
 * @Description: 支付策略类
 */
@Slf4j
public abstract class PayStrategy<Config> {
    @Autowired
    protected RefundRecordMapper refundRecordMapper;
    @Autowired
    protected PayRecordMapper payRecordMapper;

    @DubboReference
    private IAirportExpressOrderAppService airportExpressOrderAppService;
    @DubboReference
    private IHotelOrderH5Service hotelOrderH5Service;
    @DubboReference
    private IOrderService orderService;
    @DubboReference
    private IAirOrderService airOrderService;
    @DubboReference
    private ILocalFunAppOrderService iLocalFunAppOrderService;
    @DubboReference
    private IScenicOrderService scenicOrderService;
    @DubboReference
    private ITrainOrderAppService trainOrderAppService;
    @DubboReference
    private ISeriesOrderService seriesOrderService;
    @DubboReference
    private IAirshowAppOrderService iAirshowAppOrderService;
    @DubboReference
    private ISeasonOrderService iSeasonOrderService;

    @Value("${pay.local-gateway}")
    private String LOCAL_GATEWAY;
    /**
     * 配置文件class
     */
    private Class<Config> configClass;
    /**
     * 支付方式
     */
    private PaymentMethod paymentMethod;

    public PayStrategy(Class<Config> configClass, PaymentMethod paymentMethod) {
        this.configClass = configClass;
        this.paymentMethod = paymentMethod;
    }

    /**
     * 支付
     *
     * @param req
     * @return
     */
    public abstract Object pay(PayCommonReq req) throws Exception;

    /**
     * 支付回调
     *
     * @param paramMap
     * @return
     */
    protected abstract boolean payCallback(Map<String, String> paramMap);

    /**
     * 获取支付明细
     *
     * @param orderNumber
     * @return
     */
    public abstract PayDetailsDTO getPayDetails(String orderNumber);

    /**
     * 校验支付明细
     *
     * @param paramMap 回调数据
     * @return
     */
    protected abstract boolean checkPayDetails(Map<String, String> paramMap);

    /**
     * 退款
     *
     * @param req
     * @return
     */
    public abstract boolean refund(PayRefundReq req);

    /**
     * 获取退款明细
     *
     * @param orderNumber
     * @param refundNumber
     * @return
     */
    public abstract RefundDetailsDTO getRefundDetail(String orderNumber, String refundNumber);

    /**
     * 退款回调
     *
     * @param paramMap
     * @return
     */
    public abstract Object refundNotify(Map<String, String> paramMap);


    /**
     * 支付回调模板
     *
     * @param paramMap
     * @param recordId
     * @return
     */
    public final String payCallbackTemplate(Map<String, String> paramMap, Long recordId) {
        //处理回调业务逻辑
        boolean b = payCallback(paramMap);
        //查询支付记录并校验 避免恶意请求
        boolean b1 = checkPayDetails(paramMap);
        boolean success = b && b1;
        //通知对应服务
        notifyConsumers(recordId);
        return success ? "SUCCESS" : "FAILURE";
    }

    /**
     * 退款模板
     *
     * @param req
     * @return
     */
    public final RefundStatus refundTemplate(PayRefundReq req) {
        //退款申请
        boolean refund = refund(req);
        Assert.isTrue(refund, SystemError.PAY_22006);
        //查询退款记录
        RefundDetailsDTO refundDetail = getRefundDetail(req.getOrderNumber(), req.getRefundNumber());
        //退款本地记录
        refundRecord(refundDetail);
        return refundDetail != null ? refundDetail.getRefundStatus() : RefundStatus.FAILURE;
    }

    /**
     * 退款记录
     *
     * @param refundDetails
     */
    protected void refundRecord(RefundDetailsDTO refundDetails) {
        if (refundDetails == null) return;
        RefundRecord refundRecord = new RefundRecord();
        refundRecord.setOrderNumber(refundDetails.getOrderNumber());
        refundRecord.setRefundNumber(refundDetails.getRefundNumber());
        refundRecord.setRefundAmt(refundDetails.getRefundAmount());
        refundRecord.setCurrency(refundDetails.getCurrency());
        refundRecord.setRefundStatus(refundDetails.getRefundStatus().getStatus());
        refundRecord.setRefundTime(refundDetails.getRefundTime());
        refundRecord.setCreateId(0L);
        refundRecord.setModifiedId(0L);
        refundRecord.setIsDeleted(0);
        refundRecord.setId(IdWorker.getId());
        refundRecord.setGmtCreate(LocalDateTime.now());
        refundRecord.setGmtModified(LocalDateTime.now());
        refundRecordMapper.insert(refundRecord);
    }

    /**
     * 通知调用者
     *
     * @param recordId
     */
    @Async
    public void notifyConsumers(Long recordId) {
        log.info("【支付模块-通知业务模块】通知业务模块, data:{}", recordId);
        try {
            //获取记录
            PayRecord record = payRecordMapper.selectById(recordId);
            if (record == null) {
                throw new BusinessException(SystemError.PAY_22004);
            }
            NotifyConsumeReq params = NotifyConsumeReq.builder()
                    .orderNumber(record.getOrderNumber())
                    .orderStatus(PayOrderStatus.getByStatus(record.getOrderStatus()).getStatus())
                    .paymentMethod(PaymentMethod.getByCode(record.getPaymentMethod()).getCode())
                    .amount(record.getTotalFee())
                    .currency(record.getCurrency())
                    .build();
            String paramBody = JSON.toJSONString(params);
            // 查询业务订单数据
            log.info("【支付模块-通知业务模块】发送业务模块支付请求, businessTypeCode:{}, params:{}", record.getBusinessTypeCode(), paramBody);
            SystemBusinessType systemBusinessType = SystemBusinessType.parseCode(record.getBusinessTypeCode());
            Result recallPay = null;
            switch (systemBusinessType) {
                case HOTEL:
                    hotelOrderH5Service.payment(params);
                    break;
                case AIR_EXPRESS:
                    recallPay = airportExpressOrderAppService.payment(params);
                    break;
                case COMMODITY:
                    recallPay = orderService.payment(params);
                    break;
                case AIR_TICKET:
                    recallPay = airOrderService.payment(params);
                    break;
                case TRAIN_TICKET:
                    recallPay = trainOrderAppService.payment(params);
                    break;
                case BUS_TICKET:
//                callPayDTO = busOrderService.getCallPayInfo(callPayReq.getId());
                    break;
                case SHIP_TICKET:
//                callPayDTO = shipOrderService.getCallPayInfo(callPayReq.getId());
                    break;
                case ATTRACTION_TICKET:
                    recallPay = scenicOrderService.payment(params);
                    break;
                case LOCAL_FUN:
                    recallPay = iLocalFunAppOrderService.payment(params);
                    break;
                case VISA:
//                callPayDTO = getCallPayInfo(callPayReq.getId());
                    break;
                case OUTBOUND_TRAVEL:
                    //出境游
                    recallPay = seriesOrderService.payment(params);
                    break;
                case INBOUND_TRAVEL:
                    //入境游
                    recallPay = seriesOrderService.payment(params);
                    break;
                case AIR_SHOW:
                    // 珠海航展
                    recallPay = iAirshowAppOrderService.payment(params);
                    break;
                case FIXED_PACKAGE_TICKET:
                    // 固定套票
                    recallPay = iSeasonOrderService.payment(params);
                    break;
                default:
                    recallPay = Result.failed(SystemError.SYS_437);
            }
            log.info("【支付模块-通知业务模块】通知支付消费者返回结果, data:{}", JSON.toJSONString(recallPay));
        } catch (Exception e) {
            log.info("【支付模块-通知业务模块】通知支付异常", e);
        }
    }


    /**
     * 获取Config
     *
     * @return
     */
    protected Config getConfig() {
        IPayConfigService bean = ApplicationContextProvider.getBean(IPayConfigService.class);
        if (bean == null) throw new BusinessException(SystemError.PAY_22000);
        return bean.getConfig(paymentMethod, configClass);
    }
}
